package com.tianji.learning.service.impl;

import cn.hutool.db.DbRuntimeException;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningPlanDTO;
import com.tianji.learning.domain.dto.LearningRecordFormDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.PlanStatus;
import com.tianji.learning.enums.SectionType;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.utils.LearningRecordDelayTaskHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author yangyu
 * @since 2023-07-22
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {

    private final ILearningLessonService lessonService;

    private final CourseClient courseClient;

    private final LearningRecordDelayTaskHandler taskHandler;


    /**
     * 1.查询指定课程的学习记录 - 供course课程微服务远程调用
     *
     * @param courseId 课程id
     * @return 学习课表进度信息
     */
    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        //1.获取登录用户id
        Long userId = UserContext.getUser();

        //2.根据用户ID和课程ID查询：最近学习小节id，课表id  （查询课表）
        LearningLesson lesson = lessonService.queryByUserAndCourseId(userId, courseId);

        //3.根据用户ID和课表ID查询：所有学习记录 （查询学习记录表）
        List<LearningRecord> recordList = list(Wrappers.<LearningRecord>lambdaQuery()
                .eq(LearningRecord::getUserId, userId)
                .eq(LearningRecord::getLessonId, lesson.getId()));

        //4.封装小节信息及学习进度
        List<LearningRecordDTO> recordDTOS = BeanUtils.copyList(recordList, LearningRecordDTO.class);

        //5..封装DTO返回
        LearningLessonDTO learningLessonDTO = new LearningLessonDTO();  //学习课表进度信息
        learningLessonDTO.setId(courseId);  //课表id
        learningLessonDTO.setLatestSectionId(lesson.getLatestSectionId());  //最近学习的小节id
        learningLessonDTO.setRecords(recordDTOS);
        return learningLessonDTO;
    }


    /**
     * 2.提交学习记录
     *
     * @param recordDTO 学习记录DTO
     */
    @Override
    public void addLearningRecord(LearningRecordFormDTO recordDTO) {
        //1.获取登录用户id
        Long userId = UserContext.getUser();

        //定义变量，记录是否有新学完的小节
        boolean finished = false;

        //2.判断小节类型是考试还是视频
        if (recordDTO.getSectionType() == SectionType.EXAM) {
            //2.1.处理考试逻辑
            finished = handleExamRecord(recordDTO, userId);

        } else {
            //2.2.处理视频逻辑：
            finished = handleVideoRecord(recordDTO, userId);
        }

        if (!finished) {
            //没有新学完的小节，无需更新课表中的学习进度，延迟任务里面处理了课表数据
            return;
        }

        //3.有新的小节学完。处理课表数据
        handleLearningLessonsChanges(recordDTO);
    }


    /**
     * 处理视频逻辑
     *
     * @param recordDTO 学习记录DTO
     * @param userId    用户id
     * @return 是否有新的小节学完
     */
    private boolean handleVideoRecord(LearningRecordFormDTO recordDTO, Long userId) {
        // 1.查询旧的学习记录
        LearningRecord oldRecord = queryOldRecord(recordDTO.getLessonId(), recordDTO.getSectionId());

        //2.判断学习记录是否存在
        if (oldRecord == null) {
            //3.不存在，则新增学习记录（除了完成学习的时间，完成的状态，其他都要填）
            //第一次提交，是不可能学完的。因为每15秒提交一次请求。
            boolean success = saveRecord(recordDTO, userId);
            if (!success) {
                throw new DbException("新增学习记录失败！");
            }
            //没有新的小节学完
            return false;
        }


        //4.学习记录已经存在，则更新 (方案优化 改造点 2)：
        /*
         * 4.1.判断当前小节是不是第一次学完？？
         * 满足条件：学习记录中，小节的旧状态是未完成，并且播放进度超过50%（注意：小节状态默认为false未完成）
         * 旧状态==false未完成  &&  当前观看时长 * 2 >= 视频总时长
         */
        boolean finished = !oldRecord.getFinished() && recordDTO.getMoment() * 2 >= recordDTO.getDuration();
        log.debug("finished状态：" + finished);
        if (!finished) { //false
            //4.2.不是第一次学完
            //学习记录缓存到redis，提交延迟检测任务。
            LearningRecord record = new LearningRecord();
            record.setId(oldRecord.getId()); //本次提交学习记录id
            record.setLessonId(recordDTO.getLessonId()); //课表id
            record.setSectionId(recordDTO.getSectionId()); //小节id
            record.setMoment(recordDTO.getMoment()); //本次提交播放进度
            record.setFinished(oldRecord.getFinished());
            taskHandler.addLearningRecordTask(record);
            return false;  //没有新的小节学完
        }

        //4.3.第一次学完
        //更新mysql数据
        update(Wrappers.<LearningRecord>lambdaUpdate()
                .eq(LearningRecord::getId, oldRecord.getId())   //学习记录唯一id
                .set(LearningRecord::getMoment, recordDTO.getMoment()) //播放进度
                .set(LearningRecord::getUpdateTime, recordDTO.getCommitTime()) //最近观看时间
                .set(LearningRecord::getFinished, true)); //小节状态为：已完成学习

        //清理redis缓存
        taskHandler.cleanRecordCache(recordDTO.getLessonId(), recordDTO.getSectionId());
        return true;  //有新的小节学完
    }


    /**
     * 处理考试逻辑
     *
     * @param recordDTO 学习记录DTO
     * @param userId    用户id
     */
    private boolean handleExamRecord(LearningRecordFormDTO recordDTO, Long userId) {
        //3.1.学习记录表--   新增学习记录
        boolean success = saveRecord(recordDTO, userId);
        if (!success) {
            throw new DbException("新增考试记录失败！");
        }
        return true;  //有新的小节学完
    }

    /**
     * 方案优化 改造点 1
     * 查询旧的学习记录
     *
     * @param lessonId
     * @param sectionId
     * @return
     */
    private LearningRecord queryOldRecord(Long lessonId, Long sectionId) {
        //1.查询缓存
        LearningRecord record = taskHandler.readRecordCache(lessonId, sectionId);

        //2.如果命中，直接返回
        if (record != null) {
            return record;
        }

        //3.未命中，查询数据库
        record = getOne(Wrappers.<LearningRecord>lambdaQuery()
                .eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getSectionId, sectionId));

        // 4.写入缓存
        log.debug("queryOldRecord方法中的finished:{}", record.getFinished());
        taskHandler.writeRecordCache(record);
        return record;
    }


    //第一次学完，处理课表数据
    private void handleLearningLessonsChanges(LearningRecordFormDTO recordDTO) {
        log.debug("有新完成的小节");
        //1.查询当前课表
        LearningLesson lesson = lessonService.getById(recordDTO.getLessonId());
        if (lesson == null) {
            throw new BizIllegalException("课程不存在，无法更新数据！");
        }

        //2.有新完成的小节，需要查询课程数据，用来判断是否学完全部小节:
        CourseFullInfoDTO cInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
        if (cInfo == null) {
            throw new BizIllegalException("课程不存在，无法更新数据！");
        }

        //3.比较课程是否全部学完：已学习小节数量 >= 课程总节数
        Integer learnedSections = lesson.getLearnedSections() + 1;//已学习小节数量
        boolean allLearned = learnedSections >= cInfo.getSectionNum();

        //4.更新课表，已学习小节数量+1，最近学习小节id，最近学习时间。
        lessonService.update(Wrappers.<LearningLesson>lambdaUpdate()
                .eq(LearningLesson::getId, recordDTO.getLessonId())
                //课程状态如果为0-未学习，则修改为1-学习中
                .set(lesson.getStatus() == LessonStatus.NOT_BEGIN, LearningLesson::getStatus, LessonStatus.LEARNING)
                //已学习小节+1
                .set(LearningLesson::getLearnedSections, learnedSections)
                //最近学习小节id
                .set(LearningLesson::getLatestSectionId, recordDTO.getSectionId())
                //最近学习时间
                .set(LearningLesson::getLatestLearnTime, recordDTO.getCommitTime())
                //课表更新时间
                .set(LearningLesson::getUpdateTime, recordDTO.getCommitTime())
                //课程全部学完，则修改状态为已完成
                .set(allLearned, LearningLesson::getStatus, LessonStatus.FINISHED)
                //课程全部学完，学习计划修改为没有计划
                .set(allLearned, LearningLesson::getPlanStatus, PlanStatus.NO_PLAN)
        );
    }


    /**
     * 新增学习记录
     *
     * @param recordDTO 学习记录表单DTO
     * @param userId    用户id
     * @return 插入结果
     */
    private boolean saveRecord(LearningRecordFormDTO recordDTO, Long userId) {
        //1.拷贝
        LearningRecord newRecord = BeanUtils.copyBean(recordDTO, LearningRecord.class);

        //2.判断小节类型
        if (recordDTO.getSectionType() == SectionType.VIDEO) {
            //视频：
            newRecord.setCreateTime(recordDTO.getCommitTime()); //第一次观看时间

        } else {
            //考试：
            newRecord.setFinished(true);    //本小节已完成
            newRecord.setFinishTime(recordDTO.getCommitTime());  //完成学习的时间
        }
        newRecord.setUserId(userId);
        newRecord.setUpdateTime(recordDTO.getCommitTime());  //更新时间（最近一次观看时间）
        //返回操作结果
        return save(newRecord);
    }


}
